package app.auth {
	import flash.events.TimerEvent;
	import flash.utils.Timer;

	import org.osflash.signals.Signal;

	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.HTTPStatusEvent;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.events.SecurityErrorEvent;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.net.URLRequestMethod;
	import flash.net.URLStream;
	import flash.net.URLVariables;

	/**
	 *  
	 *  1) Create a UI element to capture login data from the user.
	 *  	The UI needs to solicit a user name (email address including domain) and password. The UI should also be capable of displaying a CAPTCHA image using the URL received from Google, if one is required, and soliciting a correct answer from the user. Ideally, your UI will include a link to Google Accounts login page ("https://www.google.com/accounts/Login") in the event that the user needs to sign up for a new account or do other account maintenance.
	 *  2) Write code to generate a well-formed HTTPS POST ClientLogin request and transmit it.
	 *  	This code needs to contain logic to handle a CAPTCHA challenge and include both the logintoken and logincaptcha parameters. The application should also be able to detect when the user omits required information--or repeats incorrect data after a login failure--and display an error without sending a superfluous request.
	 *  3) Handle responses from Google.
	 *  	There are four possible responses to a login request:
	 *  		success (an HTTP 200)
	 *  		failure (an HTTP 403) with an explanatory error code
	 *  		invalid request, generally resulting from a malformed request
	 *  		failure with a CAPTCHA challenge
	 *  	A success response contains an authorization token labeled "Auth". This token must be included in all subsequent requests to the Google service for this account. Authorization tokens should be closely guarded and should not be given to any other application, as they represent access to the user's account. The time limit on the token varies depending on which service issued it.
	 *  	A failure response includes one or more error codes and a URL with the error message that can be displayed for the user. Please note that ClientLogin does not differentiate between a failure due to an incorrect password or one due to an unrecognized user name (for example, if the user has not yet signed up for an account). Your application needs to handle all possible error messages as appropriate.
	 *  	A failure response with a CAPTCHA challenge means that Google has decided, for whatever reason, that additional security measures should be taken. This response is accompanied by a CAPTCHA image URL and a token representing the specific CAPTCHA challenge.
	 *  4) Handle a CAPTCHA challenge from Google.
	 *  	To handle the challenge, the application must display the CAPTCHA image and solicit an answer from the user. To display the CAPTCHA image, use the value of CaptchaUrl returned with the failure response, prefixing it with the Google Accounts URL: "http://www.google.com/accounts/". Once the user provides an answer, the application should resend the login request, this time including the CAPTCHA token (logintoken) and the user's answer (logincaptcha). Google validates the user's answer before authorizing access to the account.
	 *  	There is an alternative for developers who do not want to manage the process's of getting and transmitting a user CAPTCHA response. In response to a CAPTCHA challenge, the application can direct the user to the Google hosted page: "https://www.google.com/accounts/DisplayUnlockCaptcha". Once the user has successfully responded to the challenge, the Google server trusts the computer in use. The application can then resend the original login request to obtain the authorization token.
	 *  	
	 *  	SERVICE NAMES:
	 *  	Google API									Service name
	 *  	Google Analytics Data APIs					analytics
	 *  	Google Apps APIs
	 *  	(Domain Information & Management)			apps
	 *  	Google Base Data API						gbase
	 *  	Google Sites Data API						jotspot
	 *  	Blogger Data API							blogger
	 *  	Book Search Data API						print
	 *  	Calendar Data API							cl
	 *  	Google Code Search Data API					codesearch
	 *  	Contacts Data API							cp
	 *  	Documents List Data API						writely
	 *  	Finance Data API							finance
	 *  	Gmail Atom feed								mail
	 *  	Health Data API								health
	 *  												weaver (H9 sandbox)
	 *  	Maps Data APIs								local
	 *  	Picasa Web Albums Data API					lh2
	 *  	Sidewiki Data API							annotateweb
	 *  	Spreadsheets Data API						wise
	 *  	Webmaster Tools API							sitemaps
	 *  	YouTube Data API							youtube
	 *  	
	 *  	ERROR MESSAGES:
	 *  	Error code			Description
	 *  	BadAuthentication	The login request used a username or password that is not recognized.
	 *  	NotVerified			The account email address has not been verified. The user will need to access their Google account directly to resolve the issue before logging in using a non-Google application.
	 *  	TermsNotAgreed		The user has not agreed to terms. The user will need to access their Google account directly to resolve the issue before logging in using a non-Google application.
	 *  	CaptchaRequired		A CAPTCHA is required. (A response with this error code will also contain an image URL and a CAPTCHA token.)
	 *  	Unknown				The error is unknown or unspecified; the request contained invalid input or was malformed.
	 *  	AccountDeleted		The user account has been deleted.
	 *  	AccountDisabled		The user account has been disabled.
	 *  	ServiceDisabled		The user's access to the specified service has been disabled. (The user account may still be valid.)
	 *  	ServiceUnavailable	The service is not available; try again later.
	 *  	
	 */
	public class ClientLogin {

		public static const ACTION_URL : String = "https://www.google.com/accounts/ClientLogin";		private static const CAPTCHA_URL : String = "http://www.google.com/accounts/";

		public static const ACCOUNT_TYPE_GOOGLE : String = "GOOGLE";// (get authorization for a Google account only) 
		public static const ACCOUNT_TYPE_HOSTED : String = "HOSTED";// (get authorization for a hosted account only) 
		public static const ACCOUNT_TYPE_HOSTED_OR_GOOGLE : String = "HOSTED_OR_GOOGLE";//  (get authorization first for a hosted account; if attempt fails, get authorization for a Google account)

		public static const CONTENT_TYPE : String = "application/x-www-form-urlencoded";

		private static const HTTP_STATUSCODE_SUCCESS : uint = 200;
		private static const HTTP_STATUSCODE_FAIL : uint = 403;

		public static const STATUS_READY : String = "STATUS_READY";
		public static const STATUS_REQUEST_SENT : String = "STATUS_REQUEST_SENT";
		public static const STATUS_RESPONSE_RECEIVED : String = "STATUS_RESPONSE_RECEIVED";
		public static const STATUS_ERROR : String = "STATUS_ERROR";
		public static const STATUS_TIMEOUT : String = "STATUS_TIMEOUT";
		public static const STATUS_AUTHENTICATED : String = "STATUS_AUTHENTICATED";
		public static const STATUS_CAPTCHA_CHALLENGE : String = "STATUS_CAPTCHA_CHALLENGE_REQUIRED";
		public static const STATUS_CAPTCHA_READY : String = "STATUS_CAPTCHA_READY";

		private var accountType : String;//	Type of account to request authorization for. Possible values are:
		private var service : String;//	Name of the Google service you're requesting authorization for. Each service using the Authorization service is assigned a name value; for example, the name associated with Google Calendar is 'cl'. This parameter is required when accessing services based on Google Data APIs. For specific service names, refer to the service documentation.
		private var source : String;//	Short string identifying your application, for logging purposes. This string should take the form: "companyName-applicationName-versionID".

		private var Email : String;//	User's full email address. It must include the domain (i.e. johndoe@gmail.com).
		private var Passwd : String;// User's password.
		private var logintoken : String;//	(optional) Token representing the specific CAPTCHA challenge. Google supplies this token and the CAPTCHA image URL in a login failed response with the error code "CaptchaRequired".
		private var CaptchaUrl:String;

		
		private var loginReq : URLRequest;
		private var loginURLLoader : URLLoader;
		private var Auth : String;
		private var loginVars : URLVariables;
		private var stream : URLStream;

		private var _statusSignal : Signal;

		private static const DEFAULT_TIMEOUT_SECS : uint = 15;
		private var _timeOutSeconds : uint = DEFAULT_TIMEOUT_SECS;
		private var timeoutTimer : Timer;

		
		
		/**
		 * ClientLogin Constructor
		 */
		public function ClientLogin(source : String = "RendallTV_testApp_v1", accountType : String = ACCOUNT_TYPE_GOOGLE, service : String = "cl") {
			trace("\n ClientLogin.ClientLogin( source, accountType, service:" + source, accountType, service + ")");
			
			this.source = source;
			this.accountType = accountType;
			this.service = service;
			
			loginVars = new URLVariables();
			loginVars.source = this.source;
			loginVars.accountType = this.accountType;
			loginVars.service = this.service;
			
			var readyTimer : Timer = new Timer(10,1);
			readyTimer.addEventListener(TimerEvent.TIMER_COMPLETE, onReadyTime, false, 0, true);
			readyTimer.start();
			//testInit();
		}

		private function onReadyTime(event : TimerEvent) : void {
			var timer : Timer = event.target as Timer;
			timer.removeEventListener(TimerEvent.TIMER_COMPLETE, onReadyTime);
			setStatus(STATUS_READY);
		}

		public function set timeOutSeconds(value : uint) : void {
			_timeOutSeconds = value;	
		}


		public function authenticate(email : String, password : String) : void {
			

			
			this.Email = email;
			this.Passwd = password;
			loginVars.Email = this.Email;
			loginVars.Passwd = this.Passwd;
			
			sendRequest();
		}

		private function sendRequest() : void {
			loginReq = new URLRequest();
			loginReq.url = ACTION_URL;
			loginReq.contentType = CONTENT_TYPE;
			
			loginReq.method = URLRequestMethod.POST;
			loginReq.data = loginVars;
			
			loginURLLoader = new URLLoader();
			//configureListeners(loginURLLoader);
			stream = new URLStream();
			configureListeners(stream);
			
			try {
				stream.load(loginReq);
				//loginURLLoader.load(loginReq);
				setStatus(STATUS_REQUEST_SENT);
			} catch (error : Error) {
				trace ("\tNot sent error : " + error);
				
				setStatus(STATUS_ERROR, error.message);
			}
			
		}

		private function configureListeners(dispatcher : EventDispatcher) : void {
			dispatcher.addEventListener(Event.COMPLETE, onResponseReceived);
			dispatcher.addEventListener(HTTPStatusEvent.HTTP_STATUS, onHTTPResponse);
			dispatcher.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
			dispatcher.addEventListener(Event.OPEN, onLoginOpen);
			if (dispatcher is URLStream) dispatcher.addEventListener(ProgressEvent.PROGRESS, onLoginStreamProgress);            else dispatcher.addEventListener(ProgressEvent.PROGRESS, onLoginProgress);
			dispatcher.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
		}

		//TODO:  I have not yet had a captcha challenge, and I'm not sure what
		// event would be signaled, if any.  ERROR or COMPLETE? 
		// In either case, I think I could get the data
		// from the stream. This is here as a reminder.
		private function onLoginStreamProgress(event : ProgressEvent) : void {
			trace("progressHandler: " + event);
			//for (var i:int = 0; i < event.bytesLoaded; i++) {
			trace("stream: " + stream.readMultiByte(event.bytesLoaded, "iso-8859-01"));
                    
            //}
		}

		
		private function onLoginProgress(event : ProgressEvent) : void {
			trace("\n ClientLogin.onLoginProgress( event:" + event + ")");
		}

		private function onLoginOpen(event : Event) : void {
			trace("\n ClientLogin.onLoginOpen( event:" + event + ")");
		}

		private function onSecurityError(event : SecurityErrorEvent) : void {
			trace("\n ClientLogin.onSecurityError( event:" + event + ")");
			setStatus(STATUS_ERROR, event.text);
		}

		private function onIOError(event : IOErrorEvent) : void {
			trace("\n ClientLogin.onIOError(" + event + ")");
			trace ("\terrorID : " + event.type);
			
			setStatus(STATUS_ERROR);
		}

		private function onHTTPResponse(event : HTTPStatusEvent) : void {
			trace("\n ClientLogin.onHTTPResponse( event:" + event + ")");
			var statusCode : int = event.status;
			trace ("\tstatusCode : " + statusCode);
			trace ("\tevent.target : " + event.target);
			
			
			
			
			switch (statusCode) {
				case HTTP_STATUSCODE_SUCCESS:
					trace("SUCCESS_STATUS");
					break;                    
				case HTTP_STATUSCODE_FAIL:
					trace("FAIL_STATUS");
					break;
				default:
					if (stream) trace ("\tstream.bytesAvailable : " + stream.bytesAvailable);
					//trace("stream: " + stream.readMultiByte(stream.bytesAvailable, "iso-8859-01"));
  
			}
		}

		private function onResponseReceived(event : Event) : void {
			var data : String;//var loader : URLLoader = URLLoader(event.target);
			
			if (event.target is URLLoader) data = URLLoader(event.target).data;
			if (event.target is URLStream) data = URLStream(event.target).readUTF();
			
			trace ("\tdata : " + data);
			
			var responseArr : Array = data.split("\n");
			var responseVars : URLVariables = new URLVariables();
			
			for each (var pair : String in responseArr) {
				var isEmptyString : Boolean = pair == null || pair.length == 0 || pair == "";
				if (isEmptyString) continue;
				
				var keyVal : Array = pair.split("=");
				var key : String = keyVal[0];
				var value : String = keyVal[1];
				responseVars[key] = value;
			}
			Auth = responseVars.Auth;
			
			
			
			setStatus(STATUS_RESPONSE_RECEIVED);
			
			if (!ClientLogin.isEmptyString(Auth)) {
				
				setStatus(STATUS_AUTHENTICATED);
			}
			
			else {
				
				if (!ClientLogin.isEmptyString(CaptchaUrl)) setStatus(STATUS_CAPTCHA_CHALLENGE, CAPTCHA_URL + CaptchaUrl);
				else throw new Error("No AuthToken or CaptchaUrl received in loader data " + data);//setStatus(STATUS_ERROR, "No Auth Token received: " + loader.data);
			}
		}

		public function get authToken() : String {
			return Auth;	
		}
		
		private static function isEmptyString(str:String):Boolean{
			return (str==null || str=="" || str.length == 0);	
			
		}

		private function setStatus(statusType : String, message : String = null) : void {
			trace("\n ClientLogin.setStatus( status:" + statusType + ")");	
			statusSignal.dispatch(statusType, message);
			
			// start/stop timeout timer
			var startStatuses : Array = [STATUS_REQUEST_SENT];
			var isStartStatus : Boolean = startStatuses.indexOf(statusType) >= 0;
			var stopStatuses : Array = [STATUS_ERROR,STATUS_RESPONSE_RECEIVED,STATUS_TIMEOUT];
			var isStopStatus : Boolean = stopStatuses.indexOf(statusType) >= 0;
			
			if (isStartStatus) startTimeout();
			else if (isStopStatus) stopTimeout();
		}
		
		public function set captchaInput(value:String):void{
			loginVars.logincaptcha = value;
			loginVars.logintoken = this.logintoken;
			setStatus(STATUS_CAPTCHA_READY);
		}

		private function startTimeout() : void {
			if (!timeoutTimer) {
				timeoutTimer = new Timer(_timeOutSeconds * 1000, 1);
				timeoutTimer.addEventListener(TimerEvent.TIMER_COMPLETE, onTimeout, false, 0, true);
			}
			
			timeoutTimer.reset();
			timeoutTimer.start();
		}

		private function onTimeout(event : TimerEvent) : void {
			setStatus(STATUS_TIMEOUT);
		}

		private function stopTimeout() : void {
			if (!timeoutTimer) return;
			timeoutTimer.stop();
			timeoutTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, onTimeout);
			timeoutTimer = null;
		}

		/*
		 * statusSignal(statusType, message);
		 */
		public function get statusSignal() : Signal {
			if (!_statusSignal) _statusSignal = new Signal(String, String);
			return _statusSignal;
		}
	}
}
